package com.zy.explore.client.infrastructure.base.jpa;

import com.zy.explore.client.infrastructure.util.DataBeanUtil;
import com.zy.explore.client.interfaces.frm.SortFieldFrm;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * description 标准JPA Entity，JPA框架持久层基类，继承它的实体类可使用JPA的CRUD方式
 *
 * @author 🇨🇳中国印🇨🇳
 * @date 2022年04月17日 01:33:12
 * @since 1.0.0
 */
@Getter
@Setter
@ToString
@MappedSuperclass
public abstract class BaseEntity<R extends BaseJpaRepository<T, PK>, T extends BaseEntity<R, T, PK>, PK extends Serializable> {

    /**
     * 禁止从外部修改此变量或变量中的任何内容
     */
    @Nullable
    private static Map<Class<?>, BaseJpaRepository<?, ?>> repositories;

    /**
     * 修改时间
     */
    @Column(name = "update_time", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'")
    @ApiModelProperty(value = "修改时间", hidden = true)
    protected Date updateTime;

    /**
     * 创建时间
     */
    @Column(name = "create_time", insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
    @ApiModelProperty(value = "创建时间", hidden = true)
    protected Date createTime;

    /**
     * 让对象自身具备保存功能
     *
     * @return 返回保存后的对象
     */
    public T save() {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        @SuppressWarnings("unchecked")
        T entity = (T) this;

        return repository.save(entity);
    }

    /**
     * 让对象自身具备findById功能
     *
     * @return 返回查找到的对象
     */
    public T findById() {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        @SuppressWarnings("unchecked")
        T entity = (T) this;
        return repository.findById(entity.getId()).orElse(null);
    }

    /**
     * 让对象自身具备findByIds功能
     *
     * @return 返回查找到的对象集合
     */
     public List<T> findByIds(List<PK> ids) {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        return repository.findAllById(ids);
     }

    /**
     * 让对象自身具备updateSelective功能
     *
     * @return 返回更新后的对象
     */
    public T updateSelective() {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        PK id = getId();

        Assert.notNull(id, "id must not be null");

        T entity = repository.findById(id).orElse(null);

        if (entity == null) {
            return null;
        }

        DataBeanUtil.copyProperties(this, entity, true);
        repository.save(entity);
        return entity;
    }

    /**
     * 让对象自身具备findAll返回集合的功能
     *
     * @return 返回查找到的对象集合
     */
    public List<T> findAll(Specification<T> specification, Sort sort) {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        return repository.findAll(specification, sort);
    }

    /**
     * 让对象自身具备findAll返回分页的功能
     *
     * @return 返回查找到的对象分页
     */
    public Page<T> findAll(Specification<T> specification, Integer pageNum, Integer pageSize, Sort sort) {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize, sort);
        return repository.findAll(specification, pageRequest);
    }

    /**
     * 让对象自身具备remove功能
     *
     */
    @SuppressWarnings("unchecked")
    public void remove() {
        @SuppressWarnings("unchecked")
        BaseJpaRepository<T, PK> repository = getRepository(BaseJpaRepository.class, getClass());
        repository.delete((T) this);
    }

    /**
     * 获取主键
     *
     * @return {@link PK}
     */
    public abstract PK getId();

    /**
     * 获取对应Entity的Repository
     *
     * @param repositoryType Repository的interface类型
     * @param entityType     Entity实现类型
     * @param <R>            返回Repository类型
     * @param <T>            Entity类型
     * @param <PK>           Entity主键类型
     * @return 此Entity对应的Repository
     * @throws IllegalStateException 对应Entity的repository不存在，或者{@link #repositories}从未初始化
     * @throws ClassCastException    对应Entity的repository并非类型{@code repositoryType}
     */
    protected static <R extends BaseJpaRepository<T, PK>, T extends BaseEntity<R, T, PK>, PK extends Serializable>
    R getRepository(Class<R> repositoryType, Class<T> entityType) {
        Map<Class<?>, BaseJpaRepository<?, ?>> repositories = BaseEntity.repositories;

        if (repositories == null) {
            throw new IllegalStateException("Repository map does not set");
        }

        BaseJpaRepository<?, ?> repository = repositories.get(entityType);

        if (repository == null) {
            throw new IllegalStateException("Repository does not exists");
        }

        return repositoryType.cast(repository);
    }

    /**
     * 注意：此方法仅用于初始化Entity到Repository的映射，不允许任何外部调用
     *
     * @param repositories 初始化映射
     */
    public static void initRepositories(Map<Class<?>, BaseJpaRepository<?, ?>> repositories) {
        Assert.notNull(repositories, "repositories must not be null");

        BaseEntity.repositories = new HashMap<>(repositories);
    }

    /**
     * Frm转Sort排序条件
     *
     * @param sortFields 排序参数对象集合
     * @return 返回JPA所需的排序对象
     */
    public static Sort getSort(List<SortFieldFrm> sortFields) {
        List<Sort.Order> orderList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(sortFields)) {
            for (SortFieldFrm sortFrm : sortFields) {
                if (StringUtils.isEmpty(sortFrm.getOrder())) {
                    continue;
                }
                orderList.add(new Sort.Order(Objects.equals(sortFrm.getOrder().toUpperCase(), "ASC") ? Sort.Direction.ASC : Sort.Direction.DESC, sortFrm.getPropName()));
            }
        } else {
            // 默认按主键倒序排序
            orderList.add(new Sort.Order(Sort.Direction.DESC, "id"));
        }
        return Sort.by(orderList);
    }                      
}
